/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "soundengineqaudio.h"

#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_QAUDIO

#include <QCoreApplication>
#include <QDebug>

#include "wavfile.h"

/*!
    \class SoundEngineQAudio
    QAudio sound engine to play sound effects.
    NOTE! SoundEngineQAudio not preferred to be used at games. Output latency of the QAudio is not sufficient.

    \sa SoundEngine
*/

const int NotifyIntervalMs = 100;

/*!
  Constructs QAudio sound engine
*/
SoundEngineQAudio::SoundEngineQAudio(QObject *parent) 
    : QObject(parent)
    , state(QAudio::StoppedState)
    , audioOutputDevice(0)
    , playPosition(0)
    , dataLength(0)
{
}

/*!
  Destructs QAudio sound engine
*/
SoundEngineQAudio::~SoundEngineQAudio()
{
    for (int i =0; i < NumberOfSounds; i++)
        delete soundsBufferArray[i];

    if (audioOutputDevice)
        delete audioOutputDevice;
}

/*!
  Gets audio duration of the sound effect as microseconds.
*/
qint64 SoundEngineQAudio::dataDuration() const
{
    qint64 result = 0;
    if (QAudioFormat() != audioFormat)
        result = audioDuration(audioFormat, dataLength);
    return result;
}

/*!
    Plays the sound effect given at the attribute \a soundEffect. \sa ::SoundEffectType.
*/
void SoundEngineQAudio::play(SoundEffectType soundEffect)
{
    if (audioOutputDevice) {
        setPlayPosition(0);
        connect(audioOutputDevice, SIGNAL(stateChanged(QAudio::State)),
                        this, SLOT(audioStateChanged(QAudio::State)));
        connect(audioOutputDevice, SIGNAL(notify()),
                        this, SLOT(audioNotify()));
        audioOutputIODevice.close();
        audioOutputIODevice.setBuffer(soundsBufferArray[soundEffect]);
        audioOutputIODevice.open(QIODevice::ReadOnly);
        dataLength = soundsLengthArray[soundEffect];
        audioOutputDevice->start(&audioOutputIODevice);
    }
}

/*!
  Sets the QAudioFormat for the sound effects. Also initializes QAudioOutput.
*/
void SoundEngineQAudio::setAudioFormat(const QAudioFormat &format)
{
    audioFormat = format;
    // Construct and initialize AudioOutput if not done yet
    if (!audioOutput()) {
        QAudioOutput *audioOutput = new QAudioOutput(QAudioDeviceInfo::defaultOutputDevice(), format, this);
        audioOutput->setNotifyInterval(NotifyIntervalMs);
        setAudioOutput(audioOutput);
    }
}

/*!
  Gets QAudioOutput
*/
QAudioOutput* SoundEngineQAudio::audioOutput()
{
    return audioOutputDevice;
}

/*!
  Sets QAudioOutput
*/
void SoundEngineQAudio::setAudioOutput(QAudioOutput *audioOutput)
{
    audioOutputDevice = audioOutput;
}

/*!
  Slot for the QAudioOutput notify() signals.
*/
void SoundEngineQAudio::audioNotify()
{
    const qint64 playPosition = qMin(dataDuration(), audioOutputDevice->processedUSecs());

    setPlayPosition(playPosition);

    if (dataDuration() == playPosition)
        stopPlayback();
}

/*!
  Slot for the QAudioOutput stateChanged() signals.
*/
void SoundEngineQAudio::audioStateChanged(QAudio::State state)
{
    if (QAudio::StoppedState == state) {
        // Check error
        QAudio::Error error = QAudio::NoError;
        error = audioOutputDevice->error();
        if (QAudio::NoError != error) {
            qWarning() << "Error on output: " << error;
            reset();
            return;
        }
    }
    setState(state);
}

/*!
  Resets playback to initial state. Any ongoing playback is set to stopped state.
*/
void SoundEngineQAudio::reset()
{
    stopPlayback();
    setState(QAudio::StoppedState);
    setAudioFormat(QAudioFormat());
    dataLength = 0;
}

/*!
  Adds sound effect data to buffer array.
*/
void SoundEngineQAudio::addEffectBuffer(SoundEffectType effectType, QByteArray *buffer)
{
    soundsBufferArray[effectType] = buffer;
}

/*!
  Adds sound effect length to array.
*/
void  SoundEngineQAudio::setSoundLength(SoundEffectType effectType, qint64 length)
{
    soundsLengthArray[effectType] = length;
}

/*!
  Stops the sound playback.
*/
void SoundEngineQAudio::stopPlayback()
{
    if (audioOutputDevice) {
        // Note!!: stop freezed the game for a moment at the emulator
        audioOutputDevice->stop();
        QCoreApplication::instance()->processEvents();
        audioOutputDevice->disconnect();
        setPlayPosition(0);
    }
}

/*!
  Sets playback state.
*/
void SoundEngineQAudio::setState(QAudio::State state)
{
    const bool changed = (state != state);
    state = state;
    if (changed)
        emit stateChanged(state);
}

/*!
  Sets playback position given at \a position as microseconds.
*/
void SoundEngineQAudio::setPlayPosition(qint64 position)
{
    playPosition = position;
}

/*!
  Gets audio duration for given \a format and amount of \a bytes as microseconds.
*/
qint64 SoundEngineQAudio::audioDuration(const QAudioFormat &format, qint64 bytes)
{
    return (bytes * 1000000) 
        / (format.frequency() * format.channels() * (format.sampleSize() / 8));
}

#endif  // SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_QAUDIO
